/**
 * Copyright 2003-2007 Jive Software.
 *
 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jivesoftware.smackx.search;

import org.jivesoftware.smack.PacketCollector;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.Connection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketIDFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smackx.Form;
import org.jivesoftware.smackx.FormField;
import org.jivesoftware.smackx.ReportedData;
import org.jivesoftware.smackx.packet.DataForm;
import org.xmlpull.v1.XmlPullParser;

/**
 * Implements the protocol currently used to search information repositories on
 * the Jabber network. To date, the jabber:iq:search protocol has been used
 * mainly to search for people who have registered with user directories (e.g.,
 * the "Jabber User Directory" hosted at users.jabber.org). However, the
 * jabber:iq:search protocol is not limited to user directories, and could be
 * used to search other Jabber information repositories (such as chatroom
 * directories) or even to provide a Jabber interface to conventional search
 * engines.
 * <p/>
 * The basic functionality is to query an information repository regarding the
 * possible search fields, to send a search query, and to receive search
 * results.
 * 
 * @author Derek DeMoro
 */
public class UserSearch extends IQ {

	/**
	 * Creates a new instance of UserSearch.
	 */
	public UserSearch() {
	}

	public String getChildElementXML() {
		StringBuilder buf = new StringBuilder();
		buf.append("<query xmlns=\"jabber:iq:search\">");
		buf.append(getExtensionsXML());
		buf.append("</query>");
		return buf.toString();
	}

	/**
	 * Returns the form for all search fields supported by the search service.
	 * 
	 * @param con
	 *            the current Connection.
	 * @param searchService
	 *            the search service to use. (ex. search.jivesoftware.com)
	 * @return the search form received by the server.
	 * @throws org.jivesoftware.smack.XMPPException
	 *             thrown if a server error has occurred.
	 */
	public Form getSearchForm(Connection con, String searchService)
			throws XMPPException {
		UserSearch search = new UserSearch();
		search.setType(IQ.Type.GET);
		search.setTo(searchService);

		PacketCollector collector = con
				.createPacketCollector(new PacketIDFilter(search.getPacketID()));
		con.sendPacket(search);

		IQ response = (IQ) collector.nextResult(SmackConfiguration
				.getPacketReplyTimeout());

		// Cancel the collector.
		collector.cancel();
		if (response == null) {
			throw new XMPPException("No response from server on status set.");
		}
		if (response.getError() != null) {
			throw new XMPPException(response.getError());
		}
		return Form.getFormFrom(response);
	}

	/**
	 * Sends the filled out answer form to be sent and queried by the search
	 * service.
	 * 
	 * @param con
	 *            the current Connection.
	 * @param searchForm
	 *            the <code>Form</code> to send for querying.
	 * @param searchService
	 *            the search service to use. (ex. search.jivesoftware.com)
	 * @return ReportedData the data found from the query.
	 * @throws org.jivesoftware.smack.XMPPException
	 *             thrown if a server error has occurred.
	 */
	public ReportedData sendSearchForm(Connection con, Form searchForm,
			String searchService) throws XMPPException {
		UserSearch search = new UserSearch();
		search.setType(IQ.Type.SET);
		search.setTo(searchService);
		search.addExtension(searchForm.getDataFormToSend());

		PacketCollector collector = con
				.createPacketCollector(new PacketIDFilter(search.getPacketID()));

		con.sendPacket(search);

		IQ response = (IQ) collector.nextResult(SmackConfiguration
				.getPacketReplyTimeout());

		// Cancel the collector.
		collector.cancel();
		if (response == null) {
			throw new XMPPException("No response from server on status set.");
		}
		if (response.getError() != null) {
			return sendSimpleSearchForm(con, searchForm, searchService);
		}

		return ReportedData.getReportedDataFrom(response);
	}

	/**
	 * Sends the filled out answer form to be sent and queried by the search
	 * service.
	 * 
	 * @param con
	 *            the current Connection.
	 * @param searchForm
	 *            the <code>Form</code> to send for querying.
	 * @param searchService
	 *            the search service to use. (ex. search.jivesoftware.com)
	 * @return ReportedData the data found from the query.
	 * @throws org.jivesoftware.smack.XMPPException
	 *             thrown if a server error has occurred.
	 */
	public ReportedData sendSimpleSearchForm(Connection con, Form searchForm,
			String searchService) throws XMPPException {
		SimpleUserSearch search = new SimpleUserSearch();
		search.setForm(searchForm);
		search.setType(IQ.Type.SET);
		search.setTo(searchService);

		PacketCollector collector = con
				.createPacketCollector(new PacketIDFilter(search.getPacketID()));

		con.sendPacket(search);

		IQ response = (IQ) collector.nextResult(SmackConfiguration
				.getPacketReplyTimeout());

		// Cancel the collector.
		collector.cancel();
		if (response == null) {
			throw new XMPPException("No response from server on status set.");
		}
		if (response.getError() != null) {
			throw new XMPPException(response.getError());
		}

		if (response instanceof SimpleUserSearch) {
			return ((SimpleUserSearch) response).getReportedData();
		}
		return null;
	}

	/**
	 * Internal Search service Provider.
	 */
	public static class Provider implements IQProvider {

		/**
		 * Provider Constructor.
		 */
		public Provider() {
			super();
		}

		public IQ parseIQ(XmlPullParser parser) throws Exception {
			UserSearch search = null;
			SimpleUserSearch simpleUserSearch = new SimpleUserSearch();

			boolean done = false;
			while (!done) {
				int eventType = parser.next();
				if (eventType == XmlPullParser.START_TAG
						&& parser.getName().equals("instructions")) {
					buildDataForm(simpleUserSearch, parser.nextText(), parser);
					return simpleUserSearch;
				} else if (eventType == XmlPullParser.START_TAG
						&& parser.getName().equals("item")) {
					simpleUserSearch.parseItems(parser);
					return simpleUserSearch;
				} else if (eventType == XmlPullParser.START_TAG
						&& parser.getNamespace().equals("jabber:x:data")) {
					// Otherwise, it must be a packet extension.
					search = new UserSearch();
					search.addExtension(PacketParserUtils.parsePacketExtension(
							parser.getName(), parser.getNamespace(), parser));

				} else if (eventType == XmlPullParser.END_TAG) {
					if (parser.getName().equals("query")) {
						done = true;
					}
				}
			}

			if (search != null) {
				return search;
			}
			return simpleUserSearch;
		}
	}

	private static void buildDataForm(SimpleUserSearch search,
			String instructions, XmlPullParser parser) throws Exception {
		DataForm dataForm = new DataForm(Form.TYPE_FORM);
		boolean done = false;
		dataForm.setTitle("User Search");
		dataForm.addInstruction(instructions);
		while (!done) {
			int eventType = parser.next();

			if (eventType == XmlPullParser.START_TAG
					&& !parser.getNamespace().equals("jabber:x:data")) {
				String name = parser.getName();
				FormField field = new FormField(name);

				// Handle hard coded values.
				if (name.equals("first")) {
					field.setLabel("First Name");
				} else if (name.equals("last")) {
					field.setLabel("Last Name");
				} else if (name.equals("email")) {
					field.setLabel("Email Address");
				} else if (name.equals("nick")) {
					field.setLabel("Nickname");
				}

				field.setType(FormField.TYPE_TEXT_SINGLE);
				dataForm.addField(field);
			} else if (eventType == XmlPullParser.END_TAG) {
				if (parser.getName().equals("query")) {
					done = true;
				}
			} else if (eventType == XmlPullParser.START_TAG
					&& parser.getNamespace().equals("jabber:x:data")) {
				search.addExtension(PacketParserUtils.parsePacketExtension(
						parser.getName(), parser.getNamespace(), parser));
				done = true;
			}
		}
		if (search.getExtension("x", "jabber:x:data") == null) {
			search.addExtension(dataForm);
		}
	}

}
